iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
Modern Web

Three.js × WebGL 實戰:打造 HD-2D 像素地下城與即時光影系統系列 第 7 篇

Day 7:打破平坦畫布!用陣列與專屬像素圖生成立體地形

  • 分享至 

  • xImage
  •  

昨天我們鋪設了一片 100x100 的廣大平原,作為未來地圖生成的基礎。然而,這塊畫布目前面臨一個物理限制:它是一塊絕對平坦的單一幾何體,無法呈現出《歧路旅人》那種泥土小徑、草地邊緣與高低落差交織的豐富層次。

為了解決這個問題,今天我們要將昨天的巨大平原「網格化」,引入 2D 遊戲最核心的圖塊 (Tiles) 概念。我們將利用二維陣列作為設計圖,可讓程式碼自動拼裝出高低起伏的地形。

【定義地形設計圖 (Map Grid)】

首先,請打開 src/main.js,將昨天 floor(大地板)相關的程式碼全部刪除。我們不再依賴單一平面,而是改用一個二維陣列來定義地形。
在這個陣列中,數字代表了不同的地貌:

0:草地

1:泥土小路

2:稍微隆起的石頭或障礙物

可自行定義需要的圖案

    // 1. 定義地圖陣列 (這裡先用 12x12 作為示範,未來可以無限擴張)
const mapGrid = [
  [0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 2, 2, 1, 1, 0, 0, 0, 0, 0],
  [0, 0, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0],
  [0, 0, 2, 1, 1, 0, 0, 1, 1, 0, 0, 0],
  [0, 2, 2, 1, 0, 0, 0, 0, 1, 1, 0, 0],
  [0, 2, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0],
  [0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0],
  [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0],
  [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
  [0, 0, 0, 0, 0, 0, 2, 2, 1, 1, 0, 0],
  [0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];

【Step 2:載入專屬像素貼圖】
請將準備好的三張圖片(grass.png, dirt.png, stone.png)放進專案的 public 資料夾中。
接著,我們透過 TextureLoader 把它們載入,並務必開啟 NearestFilter,這能確保你的像素藝術在 3D 空間中依然保持銳利、完美的顆粒感,而不會被引擎模糊化。

// 2. 準備建材的幾何體與材質
const tileSize = 1;
const tileGeo = new THREE.BoxGeometry(tileSize, 1, tileSize);

const textureLoader = new THREE.TextureLoader();

// 載入圖片並保持像素銳利化
const grassTex = textureLoader.load('/grass.png');
grassTex.magFilter = THREE.NearestFilter;

const dirtTex = textureLoader.load('/dirt.png');
dirtTex.magFilter = THREE.NearestFilter;

const stoneTex = textureLoader.load('/stone.png');
stoneTex.magFilter = THREE.NearestFilter;

// 建立對應的標準材質
const materials = {
  0: new THREE.MeshStandardMaterial({ map: grassTex }), // 草地
  1: new THREE.MeshStandardMaterial({ map: dirtTex }),  // 泥土
  2: new THREE.MeshStandardMaterial({ map: stoneTex })  // 石塊
};

【Step 3:陣列掃描與實例化地形】
最後是今天的重頭戲。利用雙層 for 迴圈掃描 mapGrid,根據讀取到的數字生成對應的方塊。請特別注意程式碼中對 y 軸(高度)的微調,這會讓石頭隆起,讓地圖瞬間具備微縮模型的立體感!

// 3. 掃描陣列並生成 3D 實體
const mapSize = mapGrid.length;

for (let row = 0; row < mapSize; row++) {
  for (let col = 0; col < mapGrid[row].length; col++) {
    const tileType = mapGrid[row][col];
    
    // 生成方塊並自動套用對應的像素貼圖
    const tile = new THREE.Mesh(tileGeo, materials[tileType]);
    
    // 計算 X 與 Z 座標 (讓整張地圖置中)
    tile.position.x = col - (mapSize / 2) + 0.5;
    tile.position.z = row - (mapSize / 2) + 0.5;
    
    // --- 關鍵:高度微調 ---
    if (tileType === 2) {
      // 石頭障礙物稍微加高
      tile.scale.y = 1.5; 
      tile.position.y = 1.5 / 2;
    } else {
      // 草地與泥土是平的
      tile.position.y = 1 / 2;
    }

    scene.add(tile);
  }
}

【本日結語】
今天的地圖主要還是以陣列的方式完成,雖然能夠快速建立出基本的地圖結構,但隨著地圖規模變大,手動輸入的方式也變得越來越繁瑣。

因此,明天預計會著重在解決「地圖太小」以及「手動建立地圖效率太低」的問題,嘗試改用演算法來自動生成更大、更有變化的地圖。

我們明天見。


上一篇
Day 6:捨棄上帝之手!打造俯視視角與邊界牆壁
下一篇
Day 8:程式化生成無邊際的地宮!50x50 網格與隨機地貌演算法
系列文
Three.js × WebGL 實戰:打造 HD-2D 像素地下城與即時光影系統 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言